#include "FileTool.h"
#include <vector>
#include <filesystem>
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#if defined(__GNUC__) || !defined(_WIN32)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#ifndef _WIN32
#include <pwd.h>
#include <grp.h>
#else //_WIN32
#include <windows.h>
#ifdef UNICODE
#define STRING std::wstring
#define T(x) L##x
#define CERR std::wcerr
#define CHAR_T wchar_t
#define COUT std::wcout
#else
#define STRING std::string
#define T(x) x
#define CERR std::cerr
#define CHAR_T char
#define COUT std::cout
#endif
#endif //_WIN32

#include <fstream>
using namespace std;
namespace fs = std::filesystem;
namespace internal
{
#ifdef _WIN32
    std::string wstringToString(const std::wstring &wstr)
    {
        int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
        std::string strTo(size_needed, 0);
        WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
        return strTo;
    }

    std::wstring stringToWstring(const std::string &str)
    {
        int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
        std::wstring wstrTo(size_needed, 0);
        MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
        return wstrTo;
    }

    STRING ToString(const std::string &str)
    {
#ifdef UNICODE
        return stringToWstring(str);
#else
        return str;
#endif
    }

    std::string GetAccountNameFromSid(PSID pSid)
    {
        CHAR_T accountName[256];
        CHAR_T domainName[256];
        DWORD accountNameSize = sizeof(accountName) / sizeof(CHAR_T);
        DWORD domainNameSize = sizeof(domainName) / sizeof(CHAR_T);
        SID_NAME_USE sidNameUse;
        std::string ans = "Unknown";
        if (LookupAccountSid(NULL, pSid, accountName, &accountNameSize, domainName, &domainNameSize, &sidNameUse))
        {
#ifdef UNICODE
            ans = wstringToString(STRING(accountName));
#else
            ans = STRING(accountName);
#endif
        }
        return ans;
    }

    // 获取文件的硬链接数量
    // 参数: filePath - 文件路径
    bool GetFileHardLinkCount(const STRING &filePath)
    {
        // 打开文件，只读方式，允许其他程序读写该文件
        HANDLE hFile = CreateFile(filePath.data(),
                                  GENERIC_READ,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  OPEN_EXISTING,
                                  FILE_ATTRIBUTE_NORMAL,
                                  NULL);

        // 如果文件打开失败，则输出错误信息并返回
        if (hFile == INVALID_HANDLE_VALUE)
        {
            CERR << T("Error opening file: ") << GetLastError() << std::endl;
            return 0;
        }

        // 获取文件信息
        BY_HANDLE_FILE_INFORMATION fileInfo;
        if (!GetFileInformationByHandle(hFile, &fileInfo))
        {
            // 如果获取文件信息失败，则输出错误信息并关闭文件句柄后返回
            CERR << T("Error getting file information: ") << GetLastError() << std::endl;
            CloseHandle(hFile);
            return 0;
        }

        // 输出硬链接数量
        COUT << T("Number of hard links: ") << fileInfo.nNumberOfLinks << std::endl;

        // 关闭文件句柄
        CloseHandle(hFile);
        return fileInfo.nNumberOfLinks;
    }

    bool initPSDAndLengthNeeded(const STRING &filePath, PSECURITY_DESCRIPTOR &pSD, DWORD &dwLengthNeeded)
    {
        // 首次调用 GetFileSecurity，仅为了获取安全描述符缓冲区所需的大小
        if (!GetFileSecurity(filePath.data(), OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, NULL, 0, &dwLengthNeeded))
        {
            // 检查失败原因是否为缓冲区不足，如果不是，则输出错误信息并返回
            if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
            {
                CERR << T("Error getting file security: ") << GetLastError() << std::endl;
                return false;
            }
        }
        // 根据所需大小分配安全描述符的内存
        pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwLengthNeeded);
        if (pSD == NULL)
        {
            // 如果内存分配失败，输出错误信息并返回
            CERR << T("Error allocating memory for security descriptor.") << std::endl;
            return false;
        }
        return true;
    }

    bool getUsername(const STRING &filePath, string &username)
    {
        PSECURITY_DESCRIPTOR pSD = NULL;
        DWORD dwLengthNeeded = 0;
        int rett = initPSDAndLengthNeeded(filePath, pSD, dwLengthNeeded);
        if (!rett)
            return false;
        bool ret = true;
        // 再次调用 GetFileSecurity，这次提供足够的缓冲区来获取文件的安全信息
        if (GetFileSecurity(filePath.data(), OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, pSD, dwLengthNeeded, &dwLengthNeeded))
        {
            PSID pOwner = NULL;
            BOOL bOwnerDefaulted = FALSE;
            // 从安全描述符中获取所有者和组的SID
            if (GetSecurityDescriptorOwner(pSD, &pOwner, &bOwnerDefaulted))
            {
                // 输出所有者和组的信息
                username = GetAccountNameFromSid(pOwner);
            }
            else
            {
                // 如果获取所有者或组失败，输出错误信息
                username = string("Error getting owner or group from security descriptor");
                ret = false;
            }
        }
        else
        {
            // 如果获取文件安全信息失败，输出错误信息
            username = "Error getting file security";
            ret = false;
        }
        // 释放之前分配的内存
        LocalFree(pSD);
        return ret;
    }

    bool getGroupname(const STRING &filePath, string &groupname)
    {
        PSECURITY_DESCRIPTOR pSD = NULL;
        DWORD dwLengthNeeded = 0;
        int rett = initPSDAndLengthNeeded(filePath, pSD, dwLengthNeeded);
        if (!rett)
            return false;
        bool ret = true;
        // 再次调用 GetFileSecurity，这次提供足够的缓冲区来获取文件的安全信息
        if (GetFileSecurity(filePath.data(), OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, pSD, dwLengthNeeded, &dwLengthNeeded))
        {
            PSID pOwner = NULL;
            BOOL bOwnerDefaulted = FALSE;
            // 从安全描述符中获取所有者和组的SID
            if (GetSecurityDescriptorGroup(pSD, &pOwner, &bOwnerDefaulted))
            {
                // 输出所有者和组的信息
                groupname = GetAccountNameFromSid(pOwner);
            }
            else
            {
                // 如果获取所有者或组失败，输出错误信息
                groupname = string("Error getting owner or group from security descriptor");
                ret = false;
            }
        }
        else
        {
            // 如果获取文件安全信息失败，输出错误信息
            groupname = "Error getting file security";
            ret = false;
        }
        // 释放之前分配的内存
        LocalFree(pSD);
        return ret;
    }

#endif //_WIN32
} // namespace internal

bool FileTool::createFile(const char *path)
{
    if(path == nullptr)
        return false;
    if(fs::exists(path)){
        return false;
    }
    std::ofstream file(path);
    return file.is_open();
}

bool FileTool::createFile(const std::string &path, size_t size)
{
    if (fs::exists(path))
    {
        return false;
    }
    ofstream ofs(path, ios::binary);
    if (ofs.is_open())
    {
        ofs.seekp(size - 1);
        ofs.write("", 1);
        ofs.close();
        return true;
    }
    return false;
}

bool FileTool::deleteFile(const std::string &path)
{
    if (fs::exists(path))
    {
        return fs::remove(path);
    }
    return false;
}

void FileTool::getFileList(const char *dirPath, vector<string> &files)
{
    files.clear();
    try
    {
        if (fs::is_directory(dirPath))
        {
            for (const auto &entry : fs::directory_iterator(dirPath))
            {
                files.emplace_back(entry.path().string());
            }
        }
        else
        {
            cerr << "Not a directory: " << dirPath << endl;
        }
    }
    catch (const fs::filesystem_error &e)
    {
        cerr << "Error accessing directory: " << e.what() << endl;
    }
}

void FileTool::getPermissions(const std::filesystem::file_status &status, std::string &perm)
{
    perm.clear();
    perm.assign(10, '-');
    switch (status.type())
    {
    case fs::file_type::regular:
        perm[0] = '-';
        break;
    case fs::file_type::directory:
        perm[0] = 'd';
        break;
    case fs::file_type::symlink:
        perm[0] = 'l';
        break;
    case fs::file_type::block:
        perm[0] = 'b';
        break;
    case fs::file_type::character:
        perm[0] = 'c';
        break;
    case fs::file_type::fifo:
        perm[0] = 'p';
        break;
    case fs::file_type::socket:
        perm[0] = 's';
        break;
    default:
        perm[0] = 'x';
        break;
    }
    auto perms = status.permissions();
    if (unsigned(perms & fs::perms::owner_read))
        perm[1] = 'r';
    if (unsigned(perms & fs::perms::owner_write))
        perm[2] = 'w';
    if (unsigned(perms & fs::perms::owner_exec))
        perm[3] = 'x';
    if (unsigned(perms & fs::perms::group_read))
        perm[4] = 'r';
    if (unsigned(perms & fs::perms::group_write))
        perm[5] = 'w';
    if (unsigned(perms & fs::perms::group_exec))
        perm[6] = 'x';
    if (unsigned(perms & fs::perms::others_read))
        perm[7] = 'r';
    if (unsigned(perms & fs::perms::others_write))
        perm[8] = 'w';
    if (unsigned(perms & fs::perms::others_exec))
        perm[9] = 'x';
}

#if defined(__GNUC__) || !defined(_WIN32)
void FileTool::getPermissions(const struct stat &st, std::string &perm)
{
    perm.clear();
    perm.assign(10, '-');

    if (S_ISREG(st.st_mode))
        perm[0] = '-';
    else if (S_ISDIR(st.st_mode))
        perm[0] = 'd';
    else if (S_ISCHR(st.st_mode))
        perm[0] = 'c';
    else if (S_ISBLK(st.st_mode))
        perm[0] = 'b';
    else if (S_ISFIFO(st.st_mode))
        perm[0] = 'p';
#ifndef _WIN32
    else if (S_ISLNK(st.st_mode))
        perm[0] = 'l';
    else if (S_ISSOCK(st.st_mode))
        perm[0] = 's';
#endif
    else
        perm[0] = '?';
    if (S_IRUSR & st.st_mode)
        perm[1] = 'r';
    if (S_IWUSR & st.st_mode)
        perm[2] = 'w';
    if (S_IXUSR & st.st_mode)
        perm[3] = 'x';
    if (S_IRGRP & st.st_mode)
        perm[4] = 'r';
    if (S_IWGRP & st.st_mode)
        perm[5] = 'w';
    if (S_IXGRP & st.st_mode)
        perm[6] = 'x';
    if (S_IROTH & st.st_mode)
        perm[7] = 'r';
    if (S_IWOTH & st.st_mode)
        perm[8] = 'w';
    if (S_IXOTH & st.st_mode)
        perm[9] = 'x';
}
#endif
#ifndef _WIN32
string FileTool::getUsername(int uid)
{
    struct passwd *pw = getpwuid(uid);
    return pw ? pw->pw_name : to_string(uid);
}

std::string FileTool::getGroupname(int gid)
{
    struct group *gr = getgrgid(gid);
    return gr ? gr->gr_name : to_string(gid);
}
#endif
/*
none = 0, not_found = -1, regular = 1, directory = 2, symlink = 3,
      block = 4, character = 5, fifo = 6, socket = 7, unknown = 8
 */
string FileTool::getUsername(const std::string &path)
{
#ifndef _WIN32
    struct stat st;
    if (stat(path.c_str(), &st) == 0)
    {
        return getUsername(st.st_uid);
    }
    return string();
#else
#ifdef UNICODE
    STRING filePath = internal::stringToWstring(path);
#else
    STRING filePath = path;
#endif
    string username;
    if (internal::getUsername(filePath, username))
    {
        return username;
    }
    return string();
#endif
}

std::string FileTool::getGroupname(const std::string &path)
{
#ifndef _WIN32
    struct stat st;
    if (stat(path.c_str(), &st) == 0)
    {
        return getGroupname(st.st_gid);
    }
    return std::string();
#else
#ifdef UNICODE
    STRING filePath = internal::stringToWstring(path);
#else
    STRING filePath = path;
#endif
    string username;
    if (internal::getGroupname(filePath, username))
    {
        return username;
    }
    return string();
#endif
}

std::uintmax_t FileTool::getFileSize(const std::string &filePath)
{
    try
    {
        if (fs::exists(filePath))
        {
            std::uintmax_t fileSize = fs::file_size(filePath);
            return fileSize;
        }
        else
        {
            std::cerr << "File does not exist.\n";
        }
    }
    catch (const fs::filesystem_error &e)
    {
        std::cerr << "Error: " << e.what() << '\n';
    }
    return -1;
}

int FileTool::getHardLinks(const std::string &path)
{
#if __cplusplus >= 201703L
    return fs::hard_link_count(path);
#endif
#ifdef _WIN32
    return internal::GetFileHardLinkCount(internal::ToString(path));
#else
    struct stat st;
    if (stat(path.c_str(), &st) == -1)
    {
        return -1;
    }
    return st.st_nlink;
#endif
    return 0;
}

void xcom::getFileList(const char *dirPath, vector<string> &files)
{
    files.clear();
    try
    {
        if (fs::is_directory(dirPath))
        {
            for (const auto &entry : fs::directory_iterator(dirPath))
            {
                files.emplace_back(entry.path().filename().string());
            }
        }
        else
        {
            cerr << "Not a directory: " << dirPath << endl;
        }
    }
    catch (const fs::filesystem_error &e)
    {
        cerr << "Error accessing directory: " << e.what() << endl;
    }
}

void xcom::getPermissions(const std::filesystem::file_status &status, std::string &perm)
{
    perm.clear();
    perm.assign(10, '-');
    switch (status.type())
    {
    case fs::file_type::regular:
        perm[0] = '-';
        break;
    case fs::file_type::directory:
        perm[0] = 'd';
        break;
    case fs::file_type::symlink:
        perm[0] = 'l';
        break;
    case fs::file_type::block:
        perm[0] = 'b';
        break;
    case fs::file_type::character:
        perm[0] = 'c';
        break;
    case fs::file_type::fifo:
        perm[0] = 'p';
        break;
    case fs::file_type::socket:
        perm[0] = 's';
        break;
    default:
        perm[0] = 'x';
        break;
    }
    auto perms = status.permissions();
    if (unsigned(perms & fs::perms::owner_read))
        perm[1] = 'r';
    if (unsigned(perms & fs::perms::owner_write))
        perm[2] = 'w';
    if (unsigned(perms & fs::perms::owner_exec))
        perm[3] = 'x';
    if (unsigned(perms & fs::perms::group_read))
        perm[4] = 'r';
    if (unsigned(perms & fs::perms::group_write))
        perm[5] = 'w';
    if (unsigned(perms & fs::perms::group_exec))
        perm[6] = 'x';
    if (unsigned(perms & fs::perms::others_read))
        perm[7] = 'r';
    if (unsigned(perms & fs::perms::others_write))
        perm[8] = 'w';
    if (unsigned(perms & fs::perms::others_exec))
        perm[9] = 'x';
}
